//
//  KLEngine.m
//  WebRTCDemo_iOS
//
//  Created by szblsx2 on 2020/3/5.
//  Copyright © 2020 szblsx2. All rights reserved.
//

#import "KLEngine.h"
#import "KLPeerLocal.h"
#import "KLPeerRemote.h"
#import "KLPeerSrceen.h"
#import "Reachability.h"

@interface KLEngine ()<KLClientDelegate,KLPeerLocalDelegate,KLPeerRemoteDelegate>
//我的信息
@property (nonatomic, strong) KLMeetUser *myInfo;
//服务器地址
@property (nonatomic, copy) NSString *server;
//端口号
@property (nonatomic, copy) NSString *port;
//房间id
@property (nonatomic, copy) NSString *roomId;
//房间密码
@property (nonatomic, copy) NSString *password;
//信令对象
@property (nonatomic, strong) KLClient *mKLClient;
//连接工厂
@property (nonatomic, strong) RTCPeerConnectionFactory *mPeerConnectionFactory;
//ice服务器地址
@property (nonatomic, strong) NSMutableArray <RTCIceServer *> *iceServers;
//本端连接
@property (nonatomic, strong) KLPeerLocal *mKLPeerLocal;
//本端连接
@property (nonatomic, strong) KLPeerSrceen *mKLPeerSrceen;
//远端混音连接
@property (nonatomic, strong) KLPeerRemote *mKLPeerMix;
//需要订阅流的用户id集合
@property (nonatomic, strong) NSArray *needSubUserIds;
//远端连接集合
@property (nonatomic, strong) NSMutableArray <KLPeerRemote *> *mKLPeerRemotes;
//网络监听
@property (nonatomic, strong) Reachability *reachability;
//网络状态
@property (nonatomic, assign) NetworkStatus netStatus;
//socket连接状态码
@property (nonatomic, assign) NSInteger socketCode;
//断线重连定时器
@property (nonatomic, strong) dispatch_source_t timer;

@end

@implementation KLEngine
{
    //临时变量 是否开启扬声器播放
    BOOL _openSpeaker;
    //临时变量 是否离开会议
    BOOL _bCloseMeet;
}

/** 初始化连接器*/
- (void)initEngineWithServer:(NSString *)server port:(nonnull NSString *)port
{
    _server = server ?: @"";
    _port = port ?: @"";
}

/**设置turn转发服务器地址*/
- (void)setupIceServers:(NSArray<RTCIceServer *> *)iceServers
{
    if (iceServers.count > 0) {
        [_iceServers removeAllObjects];
        [_iceServers addObjectsFromArray:iceServers];
    }
}


/** 创建、加入会议*/
- (void)enterMeetingWithRoomId:(NSString *)roomId
                        myInfo:(KLMeetUser *)myInfo
                    completion:(void (^)(BOOL))completion
{
    _roomId = roomId ?: @"";
    _myInfo = myInfo;
    
    __weak __typeof(self) ws = self;
       [self connectSocketComplete:^(BOOL status) {
           
           if (!status) {
               if (completion) {
                   completion(NO);
               }
               return;
           }
           self->_bCloseMeet = NO;
           //添加网络监听
           [self addNetStatusObserver];
           
           //发送加入房间信令
           [ws.mKLClient sendJoin:roomId
                              uid:ws.myInfo.userId
                             info:@{@"name" : ws.myInfo.userName ?: @"",@"head":ws.myInfo.userHead ?: @""}
                       completion:^(BOOL result) {
               
               if (!result) {
                   [ws stopTimer];
                   [ws.mKLClient close];
                   
                   if (completion) {
                       completion(NO);
                   }
                   
                   return;
               }
               
               if (completion) {
                   completion(YES);
               }
               
               //创建上行Peer
               ws.mKLPeerLocal = [[KLPeerLocal alloc] initWithClient:ws.mKLClient factory:ws.mPeerConnectionFactory iceServers:ws.iceServers];
               ws.mKLPeerLocal.roomId = ws.roomId;
               ws.mKLPeerLocal.localUser = ws.myInfo;
               ws.mKLPeerLocal.delegate = ws;
               [ws.mKLPeerLocal freePeerConnection];
               [ws.mKLPeerLocal initCapturer];
               [ws.mKLPeerLocal initPeerConnection];
               [ws.mKLPeerLocal createOffer];
               ws.mKLPeerLocal.eventHandleBlock = ^(NSDictionary * _Nonnull response) {
                   //解析response 拦截peer断线事件
                   NSInteger event = [response[@"event"] integerValue];
                   NSInteger code = [response[@"code"] integerValue];
                   if (event == 1001 && code == 201 && !self->_bCloseMeet) {
                       if (ws.netStatus == ReachableViaWWAN || ws.netStatus == ReachableViaWiFi) {
                           [ws startTimer];
                       }
                   }else{
                       [ws stopTimer];
                   }
               };
               //设置屏幕常亮
               [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
               
           }];
           
       }];
    
}


/**离开会议*/
- (void)leaveMeeting
{
    //关闭重连定时器
    [self stopTimer];
    // 移除网络监听
    [self removeNetStatusObserver];
    // 重置会议状态变量
    _bCloseMeet = YES;
    _openSpeaker = YES;
    _needSubUserIds = nil;
    __weak __typeof(self) ws = self;
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        // 释放上行Peer
        if (ws.mKLPeerLocal) {
            [ws.mKLPeerLocal sendUnPublish];
            [ws.mKLPeerLocal freePeerConnection];
            [ws.mKLPeerLocal freeCapture];
            ws.mKLPeerLocal = nil;
        }
        // 释放下行Peer
        for (KLPeerRemote *klPeerRemote in ws.mKLPeerRemotes) {
            [klPeerRemote sendUnSubscribe];
            [klPeerRemote freePeerConnection];
        }
        [ws.mKLPeerRemotes removeAllObjects];
        //释放混音peer
        if (ws.mKLPeerMix) {
            [ws.mKLPeerMix sendUnSubscribe];
            [ws.mKLPeerMix freePeerConnection];
            ws.mKLPeerMix = nil;
        }
        // 发送退出信令
        if (ws.roomId) {
            //离开房间
            [ws.mKLClient sendLeave:ws.roomId uid:ws.myInfo.userId];
        }
        dispatch_async(dispatch_get_main_queue(), ^{
            //关闭屏幕常亮
            [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
            //关闭熄屏感知
            [[UIDevice currentDevice] setProximityMonitoringEnabled:NO];
        });
    });
}

/**根据用户id选择订阅流*/
- (void)chooseMediaSoucesWithUserIds:(NSArray *)userIds
{
    if (![self.myInfo.meetingType isEqualToString:kMeetingVideo]) {
        return;
    }
    
    NSMutableString *tempMutString = [NSMutableString string];
    NSString *newUserIds = [userIds componentsJoinedByString:@""];
    
    if (_needSubUserIds && _needSubUserIds.count > 0) {
        
        for (KLPeerRemote *peerRemote in self.mKLPeerRemotes)
        {
            for (NSString *userId in _needSubUserIds)
            {
                if ([newUserIds containsString:userId])
                {
                    [tempMutString appendString:userId];
                    
                }else{
                    if ([peerRemote.userId isEqualToString:userId])
                    {
                        [peerRemote sendUnSubscribe];
                    }
                }
            }
        }
    }
    
    _needSubUserIds = userIds;
    
    if (!userIds || userIds.count == 0) {
        return;
    }
    
    if (self.mKLPeerRemotes.count == 0) {
        return;
    }
    
    for (KLPeerRemote *peerRemote in self.mKLPeerRemotes)
    {
        for (NSString *userId in userIds)
        {
            if (![tempMutString containsString:userId])
            {
                if ([peerRemote.userId isEqualToString:userId])
                {
                    [peerRemote createOffer];
                }
            }
        }
    }
}

/**释放工厂对象*/
- (void)freeSdk
{
    [self freePeerConnectFactory];
}

/**是否打开摄像头*/
- (void)turnOnCamera:(BOOL)open
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal turnOnCamera:open];
    }
}

/**切换前后置摄像头*/
- (void)switchCamera
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal switchCamera];
    }
}

/**是否打开麦克风*/
- (void)turnOnMicrophone:(BOOL)open
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal turnOnMicrophone:open];
    }
}

/**是否开启扬声器*/
- (void)whetherToOpenLoudspeaker:(BOOL)open
{
    if (_mKLPeerLocal) {
        [_mKLPeerLocal whetherToOpenLoudspeaker:open];
    }
}

/** 发送自定义视频数据*/
- (void)sendVideoSampleBuffer:(CMSampleBufferRef)sampleBuffer
{
    if (!_mKLPeerSrceen) {
        _mKLPeerSrceen = [[KLPeerSrceen alloc] initWithClient:self.mKLClient factory:self.mPeerConnectionFactory iceServers:self.iceServers];
        _mKLPeerSrceen.roomId = self.roomId;
    }
    [_mKLPeerSrceen sendVideoSampleBuffer:sampleBuffer];
}

/**会议广播*/
- (void)sendMeetingNotificationWithType:(KLNotifiType)notifiType targetUserId:(nonnull NSString *)targetUserId state:(BOOL)state
{
    [self.mKLClient sendMeetingNotification:self.roomId type:notifiType myUserId:self.myInfo.userId targetUserId:targetUserId state:state];
}

#pragma mark - KLPeerLocalDelegate
/**获取到本地视频源*/
- (void)klPeerLocal:(KLPeerLocal *)peerLocal capturerSession:(AVCaptureSession *)captureSession
{
    _myInfo.localCaptureSession = captureSession;
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.delegate && [self.delegate respondsToSelector:@selector(addLocalCapturerSession:)]) {
            [self.delegate addLocalCapturerSession:captureSession];
        }
    });
}

#pragma mark - KLPeerRemoteDelegate
/**获取到远端视频流*/
- (void)klPeerRemote:(KLPeerRemote *)peerRemote addRemoteStream:(RTCMediaStream *)mediaStream streamId:(NSString *)streamId userId:(NSString *)userId
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.delegate && [self.delegate respondsToSelector:@selector(addRemoteMediaStream:streamId:userId:)]) {
            [self.delegate addRemoteMediaStream:mediaStream streamId:streamId userId:userId];
        }
    });
}

#pragma mark - KLClientDelegate
/**处理发布流响应*/
- (void)klClient:(KLClient *)client respPublishWithMid:(NSString *)strMid answerSdp:(NSString *)sdp
{
    self.mKLPeerLocal.streamId = strMid;
    RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
    [self.mKLPeerLocal onRemoteDescription:sessionDescription];
    
}

/**处理订阅流响应*/
- (void)klClient:(KLClient *)client respSubscribeWithMid:(NSString *)strMid subId:(NSString *)subId answerSdp:(NSString *)sdp
{
    NSString *mixStreamId = [NSString stringWithFormat:@"mixffff%@#ZZZZZZ",self.roomId];
    if ([mixStreamId isEqualToString:strMid])
    {
        if (_mKLPeerMix) {
            RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
            _mKLPeerMix.subId = subId;
            [_mKLPeerMix onRemoteDescription:sessionDescription];
        }
    }else{
        for (KLPeerRemote *klPeerRemote in self.mKLPeerRemotes) {
            if ([klPeerRemote.streamId isEqualToString:strMid]) {
                klPeerRemote.subId = subId;
                RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
                [klPeerRemote onRemoteDescription:sessionDescription];
            }
        }
    }
}

/**处理获取到房间用户列表消息*/
- (void)klClient:(KLClient *)client respRoomUserList:(NSArray<KLMeetUser *> *)userList
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.delegate && [self.delegate respondsToSelector:@selector(respRoomUserList:)]) {
            [self.delegate respRoomUserList:userList];
        }
    });
}

/**处理有人加入消息*/
- (void)klClient:(KLClient *)client respPeerJoinWithNewUser:(KLMeetUser *)newUser
{
    dispatch_async(dispatch_get_main_queue(), ^{
        if (self.delegate && [self.delegate respondsToSelector:@selector(respUserJoinRoom:)]) {
            [self.delegate respUserJoinRoom:newUser];
        }
    });
}

/**处理有人离开消息*/
- (void)klClient:(KLClient *)client respPeerLeaveWithUserId:(nonnull NSString *)userId
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(respUserLeaveWithUserId:)]) {
        [self.delegate respUserLeaveWithUserId:userId];
    }
}

/**处理有流加入消息*/
- (void)klClient:(KLClient *)client respStreamAddWithStreamId:(nonnull NSString *)streamId userId:(nonnull NSString *)userId
{
    
    NSString *mixStreamId = [NSString stringWithFormat:@"mixffff%@#ZZZZZZ",self.roomId];
    if ([mixStreamId isEqualToString:streamId])
    {
        if (!_mKLPeerMix) {
            _mKLPeerMix = [self createRemotePeerWithStreamId:streamId userId:userId];
            [_mKLPeerMix createOffer];
        }
    }else{
        if ([self.myInfo.meetingType isEqualToString:kMeetingAudio])
        {
            return;
        }
        KLPeerRemote *klPeerRemote = [self createRemotePeerWithStreamId:streamId userId:userId];
        [self.mKLPeerRemotes addObject:klPeerRemote];
    }
}

/**处理有流移除消息*/
- (void)klClient:(KLClient *)client respStreamRemoveWithMid:(NSString *)strMid userId:(NSString *)userId
{
    if ([self.myInfo.meetingType isEqualToString:kMeetingAudio]){
        return;
    }

    KLPeerRemote *mKLPeerRemote;
    for (KLPeerRemote *klPeerRemote in self.mKLPeerRemotes) {
        if ([klPeerRemote.streamId isEqualToString:strMid]) {
            mKLPeerRemote = klPeerRemote;
        }
    }
    if (mKLPeerRemote) {
        //回调上层
        dispatch_async(dispatch_get_main_queue(), ^{
            if (self.delegate && [self.delegate respondsToSelector:@selector(removeRemoteMediaStreamWithStreamId:userId:)]) {
                [self.delegate removeRemoteMediaStreamWithStreamId:strMid userId:userId];
            }
        });
        [mKLPeerRemote sendUnSubscribe];
        [mKLPeerRemote freePeerConnection];
        [self.mKLPeerRemotes removeObject:mKLPeerRemote];
        NSLog(@"移除流成功 %@",strMid);
    }
}

/**处理有共享屏幕流消息*/
- (void)klClient:(KLClient *)client respScreenWithMid:(NSString *)strMid answerSdp:(NSString *)sdp
{
    self.mKLPeerSrceen.streamId = strMid;
    RTCSessionDescription *sessionDescription = [[RTCSessionDescription alloc] initWithType:RTCSdpTypeAnswer sdp:sdp];
    [self.mKLPeerSrceen onRemoteDescription:sessionDescription];
}

/**处理会议广播消息*/
- (void)klClient:(KLClient *)client respMeetingNotificationWithType:(KLNotifiType)type targetUserId:(NSString *)targetUserId state:(BOOL)state
{
    if (self.delegate && [self.delegate respondsToSelector:@selector(resMeetingNotificationWithType:targetUserId:state:)]) {
        [self.delegate resMeetingNotificationWithType:type targetUserId:targetUserId state:state];
    }
}

#pragma mark - Private Method
/**开启连接*/
- (void)connectSocketComplete:(void (^)(BOOL))success
{
    //连接socket
    NSString *urlString = [NSString stringWithFormat:@"ws://%@:%@/ws?peer=%@",_server,_port,_myInfo.userId];
    [self.mKLClient startWithServerUrl:urlString success:^(BOOL status) {
        success(status);
    }];
}

/**释放连接工厂*/
- (void)freePeerConnectFactory
{
    if (_mPeerConnectionFactory) {
        _mPeerConnectionFactory = nil;
    }
}

/**创建下行peer*/
- (KLPeerRemote *)createRemotePeerWithStreamId:(NSString *)streamId userId:(NSString *)userId
{
    KLPeerRemote *klPeerRemote = [[KLPeerRemote alloc] initWithClient:self.mKLClient factory:self.mPeerConnectionFactory iceServers:self.iceServers];
    klPeerRemote.roomId = self.roomId;
    klPeerRemote.streamId = streamId;
    klPeerRemote.userId = userId;
    klPeerRemote.openSpeaker = self.myInfo.openLoudspeaker;
    klPeerRemote.delegate = self;
    [klPeerRemote initPeerConnection];
    
    return klPeerRemote;
}

/**添加网络监听*/
- (void)addNetStatusObserver
{
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reachabilityChanged:) name:kReachabilityChangedNotification object:nil];
    if (!_reachability) {
        _reachability = [Reachability reachabilityForInternetConnection];
    }
    [_reachability startNotifier];
    
    [self updateInterfaceWithReachability:_reachability];
}

- (void)reachabilityChanged:(NSNotification *)note
{
    Reachability *curReach = [note object];
    NSParameterAssert([curReach isKindOfClass:[Reachability class]]);
    
    [self updateInterfaceWithReachability:curReach];
}

- (void)updateInterfaceWithReachability:(Reachability *)reachability
{
    NetworkStatus netStatus = [reachability currentReachabilityStatus];
    self.netStatus = netStatus;
    switch (netStatus) {
        case NotReachable:
        {
            NSLog(@"====当前网络状态不可用=======");
            [self stopTimer];
        }
            break;
        case ReachableViaWiFi:
        {
            NSLog(@"====当前网络状态为Wifi=======");
            if (!_bCloseMeet && _socketCode == 201) {
                [self startTimer];
            }else{
                [self stopTimer];
            }
        }
            break;
        case ReachableViaWWAN:
        {
            NSLog(@"====当前网络状态为流量=======");
            if (!_bCloseMeet && _socketCode == 201) {
                [self startTimer];
            }else{
                [self stopTimer];
            }
        }
            break;
    }
}

/**移除网络状况监听*/
- (void)removeNetStatusObserver
{
    if (_reachability) {
        [_reachability stopNotifier];
    }
    [[NSNotificationCenter defaultCenter] removeObserver:self name:kReachabilityChangedNotification object:nil];
}

/**开启定时器*/
- (void)startTimer
{
    if (!_timer) {
        NSLog(@"开启重连定时器");
        //创建GCD定时器
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
        _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        //每3秒执行
        dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 3.0 * NSEC_PER_SEC, 0);
        // 开启定时器
        dispatch_activate(_timer);
        //事件回调
        dispatch_source_set_event_handler(_timer, ^{
            [self reconnection];
        });
    }
    
}

/**关闭定时器*/
- (void)stopTimer
{
    if (_timer) {
        NSLog(@"关闭重连定时器");
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

/**断线重连*/
- (void)reconnection
{
    if (_socketCode == 200) {
        //重连peer
        NSLog(@"重连~~~~~~~~~~~媒体");
        [self reconnectionPeerConnection];
    }else{
        NSLog(@"重连~~~~~~~~~~~信令");
        __weak __typeof(self) ws = self;
        [self connectSocketComplete:^(BOOL status) {
            if (!status) {
                return;
            }
            NSLog(@"信令重连成功");
            //发送加入房间信令
            [ws.mKLClient sendJoin:ws.roomId
                               uid:ws.myInfo.userId
                              info:@{@"name" : ws.myInfo.userName ?: @"",@"head":ws.myInfo.userHead ?: @""}
                        completion:^(BOOL result) {
                
                if (!result) {
                    NSLog(@"关闭定时器.加入房间失败");
                    [ws stopTimer];
                    return;
                }
                //重连peer
                [ws reconnectionPeerConnection];
            }];
        }];
    }
}

/**重新连接peer*/
- (void)reconnectionPeerConnection
{
    if (!_mKLPeerLocal) {
        _mKLPeerLocal = [[KLPeerLocal alloc] initWithClient:self.mKLClient factory:self.mPeerConnectionFactory iceServers:self.iceServers];
        _mKLPeerLocal.roomId = self.roomId;
        _mKLPeerLocal.delegate = self;
    }
    [_mKLPeerLocal freePeerConnection];
    [_mKLPeerLocal initCapturer];
    [_mKLPeerLocal initPeerConnection];
    [_mKLPeerLocal createOffer];
}

#pragma mark - Getter

- (RTCPeerConnectionFactory *)mPeerConnectionFactory
{
    if (!_mPeerConnectionFactory) {
        RTCDefaultVideoEncoderFactory *encoderFactory = [[RTCDefaultVideoEncoderFactory alloc] init];
        RTCDefaultVideoDecoderFactory *decoderFactory = [[RTCDefaultVideoDecoderFactory alloc] init];
        NSArray *encodecs = [encoderFactory supportedCodecs];
        [encoderFactory setPreferredCodec:encodecs[1]];
        _mPeerConnectionFactory = [[RTCPeerConnectionFactory alloc] initWithEncoderFactory:encoderFactory decoderFactory:decoderFactory];
    }
    return _mPeerConnectionFactory;
}

- (NSMutableArray<RTCIceServer *> *)iceServers
{
    if (!_iceServers) {
        _iceServers = [NSMutableArray array];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"stun:120.238.78.214:3478"]]];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"turn:120.238.78.214:3478?transport=tcp"] username:@"demo" credential:@"123456"]];
        [_iceServers addObject:[[RTCIceServer alloc] initWithURLStrings:@[@"turn:120.238.78.214:3478?transport=udp"] username:@"demo" credential:@"123456"]];
    }
    return _iceServers;
}

- (KLClient *)mKLClient
{
    if (!_mKLClient) {
        _mKLClient = [[KLClient alloc] initWithDelegate:self];
        __weak __typeof(self) ws = self;
        //在此监听socket连接状态 如果断开判断先判断网络是否可用 如果可用则开启重连
        _mKLClient.socketStatusBlock = ^(NSInteger code) {
            ws.socketCode = code;
            if (ws.netStatus == ReachableViaWWAN || ws.netStatus == ReachableViaWiFi) {
                if (code == 201 && !self->_bCloseMeet) {
                    [ws startTimer];
                }else{
                    [ws stopTimer];
                }
            }else{
                [ws stopTimer];
            }
        };
    }
    return _mKLClient;
}

- (NSMutableArray<KLPeerRemote *> *)mKLPeerRemotes
{
    if (!_mKLPeerRemotes) {
        _mKLPeerRemotes = [NSMutableArray array];
    }
    return _mKLPeerRemotes;
}

@end
